#include <io.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "string.h"
#include <crtdbg.h>

#include "DBFilePhyPageManager.h"
#include "DBFileType.h"
#include "DBFileError.h"

DBFilePhyPageManager::DBFilePhyPageManager(void)
{
	dbfile = NULL;
}

DBFilePhyPageManager::~DBFilePhyPageManager(void)
{
}

void DBFilePhyPageManager::IncreaseFileSize()
{
	fclose(dbfile);
	int fd = _open(curFilename, _O_RDWR);
	_chsize_s(fd, pageNum*2*sizeof(DBDiskPage)+sizeof(DBFileHead)+pageNum*2*sizeof(int));
	_close(fd);
	fopen_s(&dbfile, curFilename, "r+b");

	// alloc a new freepagelink
	int* tmpFreePageLink = new int[pageNum*2];
	for(int i=0; i<pageNum; i++)
	{
		tmpFreePageLink[i] = freePageLink[i];
	}
	freePageLink = new int[pageNum*2];
	for (int i=pageNum; i<pageNum*2; i++)
		tmpFreePageLink[i] = i+1;
	tmpFreePageLink[pageNum*2-1] = LAST_FREE_PAGE;
	nextFreePage = pageNum;

	// replace the old one
	delete [] freePageLink;
	freePageLink = tmpFreePageLink;
	pageNum *= 2;
}

int DBFilePhyPageManager::DBAllocPage()
{
	int pageAlloced;
	if(nextFreePage == LAST_FREE_PAGE)
		IncreaseFileSize();
	pageAlloced = nextFreePage;
	nextFreePage = freePageLink[pageAlloced];
	freePageLink[pageAlloced] = VALID_PID;
	return pageAlloced;
}

int DBFilePhyPageManager::OpenDBFile(char* filename)
{
	if(strlen(filename) > 300)
		return ERROR_BUFFER_OVERFLOWED;
	strcpy(curFilename,  filename);

	int errorNo = fopen_s(&dbfile, filename, "rb");

	// Create A file
	if(errorNo != 0)
	{
		pageNum = DEFAULT_PAGE_NUM;
		freePageLink = new int[pageNum];
		for(int i=0; i<pageNum; i++)
			freePageLink[i] = i+1;
		freePageLink[pageNum-1] = LAST_FREE_PAGE;
		nextFreePage = 0;

		int fd = _open(filename, _O_CREAT, _S_IREAD|_S_IWRITE );
		_chsize_s(fd, pageNum*sizeof(DBDiskPage)+sizeof(DBFileHead)+pageNum*sizeof(int));
		_close(fd);

		if((errorNo = fopen_s(&dbfile, filename, "r+b")) != 0)
		{
			cout << errorNo << endl;
			return ERROR_FILE_OP_FAIL;
		}
	}
	else
	{
		fclose(dbfile);
		if((errorNo = fopen_s(&dbfile, filename, "r+b")) != 0)
		{
			cout << errorNo << endl;
			return ERROR_FILE_OP_FAIL;
		}

		// Open A file
		if(LoadHead() < 0)
		{
			cout << "Invalid file type" << endl;
			return ERROR_FILE_OP_FAIL;
		}
	}

	return 0;
}

int DBFilePhyPageManager::CloseDBFile()
{
	if(SaveHead() < 0)
		cout << "SaveFile error" << endl;
	fclose(dbfile);
	//delete [] freePageLink;

	return 0;
}

int DBFilePhyPageManager::DBDeletePage(int pid)
{
	if(IsExist(pid))
	{
		freePageLink[pid] = nextFreePage;
		nextFreePage = pid;
		return 0;
	}
	else
	{
		return -1;
	}
}

int DBFilePhyPageManager::DBReadPage(int pid, DBDiskPage* page)
{
	if(!IsExist(pid))
		return ERROR_PAGE_NOT_EXIST;

	if (fseek(dbfile, sizeof(DBFileHead)
		+sizeof(DBDiskPage)*pid, SEEK_SET))
		return ERROR_FILE_OP_FAIL;

	if(fread((char*)page, sizeof(DBDiskPage), 1, dbfile) != 1)
	{
		return ERROR_FILE_OP_FAIL;
	}
	fflush(dbfile);

	return 0;
}

int DBFilePhyPageManager::DBWritePage(int pid, DBDiskPage* page)
{
	if(!IsExist(pid))
		return ERROR_PAGE_NOT_EXIST;

	if (fseek(dbfile, sizeof(DBFileHead)
		+sizeof(DBDiskPage)*pid, SEEK_SET) != 0)
		return ERROR_FILE_OP_FAIL;

	if(fwrite((char*)page, sizeof(DBDiskPage), 1, dbfile) != 1)
		return ERROR_FILE_OP_FAIL;

	fflush(dbfile);
	
	return 0;
}

int DBFilePhyPageManager::SaveHead()
{
	// write head
	DBFileHead fileHead;
	fileHead.pageNum = pageNum;
	fileHead.magic = MAGIC_NUM;
	fileHead.nextFreePage = nextFreePage;
	if(fseek(dbfile, 0, SEEK_SET) != 0)
		return ERROR_FILE_OP_FAIL;
	if(fwrite((char*)&fileHead, sizeof(DBFileHead), 1, dbfile) != 1)
		return ERROR_FILE_OP_FAIL;
	
	// write body
	if (fseek(dbfile, sizeof(DBFileHead) 
		+ pageNum*sizeof(DBDiskPage), SEEK_SET) != 0)
		return ERROR_FILE_OP_FAIL;
	if (fwrite((char*)freePageLink, sizeof(int), pageNum, dbfile)
		!= pageNum)
			return ERROR_FILE_OP_FAIL;
	return 0;
}

int DBFilePhyPageManager::LoadHead()
{
	DBFileHead fileHead;
	int readedNum;

	// read head
	if(fseek(dbfile, 0, SEEK_SET) != 0)
		return ERROR_FILE_OP_FAIL;
	if(fread((char*)&fileHead, sizeof(DBFileHead), 1, dbfile) != 1)
		return ERROR_FILE_OP_FAIL;
	if(fileHead.magic != MAGIC_NUM)
		return ERROR_MAGICNUM_ERROR;
	if(fileHead.pageNum <= 0)
		return ERROR_ZERO_PAGENUM;
	pageNum = fileHead.pageNum;

	// read body
	freePageLink = new int[pageNum];
	if(fseek(dbfile, sizeof(DBFileHead) + sizeof(DBDiskPage)*pageNum, SEEK_SET) != 0)
		return ERROR_FILE_OP_FAIL;
	if((readedNum = fread((char*)freePageLink
		, sizeof(int), pageNum, dbfile)) != pageNum)
	{
		cout << "Error in reading: Actually Readed Num " << readedNum << endl;
		delete [] freePageLink;
		return ERROR_FILE_OP_FAIL;
	}

	nextFreePage = fileHead.nextFreePage;
	
	return 0;
}
